Setup
Load libraries
library(ggplot2)
library(tidyr)
library(dplyr)
library(Matrix)
library(Seurat)
library(cowplot)
library(patchwork)
# parallelization
library(future)
options(future.globals.maxSize= +Inf)
plan()
sequential:
- args: function (expr, envir = parent.frame(), substitute = TRUE, lazy = FALSE, seed = NULL, globals = TRUE, local = TRUE, earlySignal = FALSE, label = NULL, ...)
- tweaked: FALSE
- call: NULL
Process Human Data
import_remote_data <- function(file_url, type = "table", header = FALSE) {
con <- gzcon(url(file_url))
txt <- readLines(con)
if (type == "MM") { return (readMM(textConnection(txt))) }
if (type == "table") { return (read.table(textConnection(txt), header = header)) }
}
count_matrix_URL <- "https://ftp.ncbi.nlm.nih.gov/geo/series/GSE137nnn/GSE137537/suppl/GSE137537_counts.mtx.gz"
gene_names_URL <- "https://ftp.ncbi.nlm.nih.gov/geo/series/GSE137nnn/GSE137537/suppl/GSE137537_gene_names.txt.gz"
sample_annotations_URL <- "https://ftp.ncbi.nlm.nih.gov/geo/series/GSE137nnn/GSE137537/suppl/GSE137537_sample_annotations.tsv.gz"
human.count_matrix <- as.matrix(import_remote_data(count_matrix_URL, type = "MM"))
human.gene_names <- import_remote_data(gene_names_URL, type = "table")
human.sample_annotations <- import_remote_data(sample_annotations_URL, type = "table", header = TRUE)
human_ret_seurat
An object of class Seurat
19712 features across 20091 samples within 1 assay
Active assay: RNA (19712 features, 0 variable features)
Process Mouse Data
mouse.data <- Read10X(data.dir = "filtered_feature_bc_matrix")
dimnames(mouse.data)[[1]] <- tolower(dimnames(mouse.data)[[1]])
dimnames(mouse.data)[[2]] <- tolower(dimnames(mouse.data)[[2]])
mouse_ret_seurat <- CreateSeuratObject(counts = mouse.data,
project = "mouse_ret",
min.cells = 3,
min.features = 200)
mouse_ret_seurat
An object of class Seurat
16424 features across 4510 samples within 1 assay
Active assay: RNA (16424 features, 0 variable features)
Process Primate Data
url=https://ftp.ncbi.nlm.nih.gov/geo/series/GSE118nnn/GSE118546/suppl/GSE118546_macaque_fovea_all_10X_Jan2018.Rdata.gz
wget $url -O primate_data/GSE118546_macaque_fovea_all_10X_Jan2018.Rdata.gz
gunzip primate_data/*
install.packages( c('devtools', 'roxygen2') )
library(devtools)
library(roxygen2)
install_github( 'hb-gitified/cellrangerRkit',
auth_token = 'your_token' )
macaque_fovea_seurat
An object of class Seurat
30039 features across 111993 samples within 1 assay
Active assay: RNA (30039 features, 0 variable features)
Cleanup
rm(human.count_matrix, human.gene_names, human.sample_annotations)
rm(count_matrix_URL, gene_names_URL, sample_annotations_URL, import_remote_data)
rm(mouse.data)
rm(Count.mat_fovea, macaque_fovea)
Combine
# combine
ret.list <- list(human = human_ret_seurat, mouse = mouse_ret_seurat, macaque = macaque_fovea_seurat)
# preprocess
ret.list <- lapply(X = ret.list, FUN = function(x) {
x <- NormalizeData(x, verbose = FALSE)
x <- FindVariableFeatures(x, selection.method = "vst", nfeatures = 2000, verbose = FALSE)
})
# cleanup
rm(human_ret_seurat, mouse_ret_seurat, macaque_fovea_seurat)
Integration
plan("multiprocess", workers = 4)
ret.anchors <- FindIntegrationAnchors(object.list = ret.list, dims = 1:50, anchor.features = 1000)
plan("multiprocess", workers = 1)
ret.combined <- IntegrateData(anchorset = ret.anchors, dims = 1:50)
Integrated Analysis
plan("multiprocess", workers = 4)
DefaultAssay(ret.combined) <- "integrated"
# Run the standard workflow for visualization and clustering
ret.combined <- ScaleData(ret.combined, verbose = FALSE)
ret.combined <- RunPCA(ret.combined, npcs = 50, verbose = FALSE)
# t-SNE and Clustering
ret.combined <- RunUMAP(ret.combined, reduction = "pca", dims = 1:35)
ret.combined <- FindNeighbors(ret.combined, reduction = "pca", dims = 1:35)
ret.combined <- FindClusters(ret.combined, resolution = 0.075)
UMAP Visualization
DimPlot(ret.combined, reduction = "umap", group.by = "orig.ident")

DimPlot(ret.combined, reduction = "umap", label = TRUE)

DimPlot(ret.combined, reduction = "umap", split.by = "orig.ident", ncol = 1)

Identify Clusters with Canonical Markers
DefaultAssay(ret.combined) <- "RNA"
features <- tolower(c("Pde6a","Gnat2","Nefl","Camk2b","Thy1","Gad1","Slc6a9",
"Pcsk6","Trpm1","Sept4","Glul","Arr3","C1qa","Tm4sf1", "Mgp"))
FeaturePlot(object = ret.combined,
features = features,
pt.size = 0.1,
cols = c("lightgrey", "#F26969"),
min.cutoff = "q9",
combine = TRUE) & NoLegend() & NoAxes()

# for(i in 1:length(p)) {
# p[[i]] <- p[[i]] + NoLegend() + NoAxes()
# }
#
# cowplot::plot_grid(plotlist = p, ncol=3)
- Rod : pde6a
- AC (amacrine cell) : gad1, slc6a9
- MG (Müller glia) : glul
- BC (bipolar cell) : Trpm, camk2b
- CC (cone cell) : gnat2, arr3
- RGC (retinal ganglial cell) : nefl, thy1
- VC (vascular cell) : mgp, tm4sf1
- M (microglia) : c1qa
- HC (horizontal cell) : sept4
Markers were determined from this paper and other sources.
ret.combined <- RenameIdents(ret.combined, `0` = "MG", `1` = "Rod", `2` = "RGC",
`3` = "RGC", `4` = "BC", `5` = "CC", `6` = "BC", `7` = "AC", `8` = "BC", `9` = "RGC",
`10` = "RGC", `11`= "HC", `12` = "MG", `13` = "VC", `14` = "RGC", `15` = "RGC", `16` = "M", `17` = "RGC")
DimPlot(ret.combined, label = TRUE)

Find Differentially Expressed Genes

ret.combined$celltype.organism <- paste(Idents(ret.combined), ret.combined$orig.ident, sep = "_")
ret.combined$celltype <- Idents(ret.combined)
Idents(ret.combined) <- "celltype.organism"
cells.diffgenes <- as.list(cells.types)
cells.diffgenes <- lapply(cells.diffgenes, FUN = function(x) {
lab_human <- sprintf("%s_human_ret", x)
lab_mouse <- sprintf("%s_mouse_ret", x)
return(FindMarkers(ret.combined, ident.1 = lab_human, ident.2 = lab_mouse, verbose = FALSE))
})
Tables with the most differentially expressed genes in each cell subtype:
for(i in seq_along(cells.diffgenes)) {
print(knitr::kable(head(cells.diffgenes[[i]]),caption=cells.types[[i]]))
}
Rod
| ckb |
0 |
1.4493770 |
0.918 |
0.724 |
0 |
| hsp90aa1 |
0 |
1.3457646 |
0.854 |
0.627 |
0 |
| nrl |
0 |
1.3140138 |
0.874 |
0.635 |
0 |
| 0610009b22rik |
0 |
-0.6622860 |
0.000 |
0.130 |
0 |
| gm17018 |
0 |
-0.6831275 |
0.000 |
0.130 |
0 |
| spata1 |
0 |
-0.6929677 |
0.000 |
0.132 |
0 |
BC
| neat1 |
0 |
3.086391 |
0.793 |
0.064 |
0 |
| mtch1 |
0 |
-1.305054 |
0.000 |
0.459 |
0 |
| selenom |
0 |
-1.338108 |
0.000 |
0.480 |
0 |
| araf |
0 |
-1.342891 |
0.013 |
0.494 |
0 |
| klc3 |
0 |
-1.424615 |
0.002 |
0.500 |
0 |
| pea15a |
0 |
-1.427543 |
0.000 |
0.500 |
0 |
MG
| tf |
0 |
5.089073 |
0.962 |
0.000 |
0 |
| spp1 |
0 |
3.879036 |
0.847 |
0.003 |
0 |
| crabp1 |
0 |
3.865908 |
0.876 |
0.028 |
0 |
| gpx3 |
0 |
3.736219 |
0.869 |
0.052 |
0 |
| ftl |
0 |
3.672007 |
0.877 |
0.000 |
0 |
| actg1 |
0 |
3.639157 |
0.905 |
0.026 |
0 |
RGC
| mt-nd4 |
0 |
-5.434720 |
0 |
1 |
2e-06 |
| mt-nd5 |
0 |
-4.555808 |
0 |
1 |
2e-06 |
| mt-co1 |
0 |
-4.634061 |
0 |
1 |
2e-06 |
| malat1 |
0 |
-5.358199 |
0 |
1 |
2e-06 |
| mt-nd1 |
0 |
-5.600755 |
0 |
1 |
2e-06 |
| mt-nd2 |
0 |
-5.700498 |
0 |
1 |
2e-06 |
CC
| gm42418 |
0 |
-5.444663 |
0 |
1 |
0 |
| malat1 |
0 |
-5.893437 |
0 |
1 |
0 |
| mt-cytb |
0 |
-6.148057 |
0 |
1 |
0 |
| mt-co1 |
0 |
-4.052888 |
0 |
1 |
0 |
| mt-nd5 |
0 |
-4.170730 |
0 |
1 |
0 |
| mt-nd1 |
0 |
-4.734329 |
0 |
1 |
0 |
AC
| mt-nd5 |
0 |
-3.999182 |
0 |
1 |
0 |
| gm42418 |
0 |
-5.868449 |
0 |
1 |
0 |
| mt-co1 |
0 |
-4.023145 |
0 |
1 |
0 |
| mt-nd4 |
0 |
-5.035458 |
0 |
1 |
0 |
| mt-nd1 |
0 |
-5.173153 |
0 |
1 |
0 |
| mt-nd2 |
0 |
-5.370662 |
0 |
1 |
0 |
VC
| hla-b |
0 |
3.429125 |
0.884 |
0.00 |
0 |
| rps3a |
0 |
2.938618 |
0.826 |
0.00 |
0 |
| hla-e |
0 |
3.220179 |
0.826 |
0.00 |
0 |
| hla-a |
0 |
3.030967 |
0.812 |
0.00 |
0 |
| hla-c |
0 |
2.913989 |
0.797 |
0.00 |
0 |
| a2m |
0 |
3.322554 |
0.797 |
0.01 |
0 |
HC
| mt-nd5 |
0 |
-4.723687 |
0 |
1 |
0 |
| mt-co1 |
0 |
-4.915798 |
0 |
1 |
0 |
| mt-nd4 |
0 |
-5.757623 |
0 |
1 |
0 |
| mt-nd1 |
0 |
-5.944134 |
0 |
1 |
0 |
| gm42418 |
0 |
-6.046691 |
0 |
1 |
0 |
| mt-nd2 |
0 |
-6.094723 |
0 |
1 |
0 |
M
| ftl |
0 |
4.612295 |
0.98 |
0 |
0 |
| hla-dra |
0 |
4.664191 |
0.94 |
0 |
0 |
| hla-a |
0 |
2.899218 |
0.94 |
0 |
0 |
| hla-drb1 |
0 |
4.096260 |
0.92 |
0 |
0 |
| rps3a |
0 |
3.397725 |
0.92 |
0 |
0 |
| hla-b |
0 |
3.163581 |
0.92 |
0 |
0 |
Save as csv files
for(i in seq_along(cells.diffgenes)) {
write.csv(cells.diffgenes[[i]], sprintf("results/%d_%s.csv", i, cells.types[[i]]))
}
genes_to_plot <- 3
for (i in seq_along(cells.types)) {
print(FeaturePlot(object = ret.combined,
features = rownames(cells.diffgenes[[i]])[1:genes_to_plot],
split.by = "orig.ident",
max.cutoff = 3,
cols = c("grey", "red"),
pt.size = 0.07,
combine = TRUE,
label.size = 0.5
) + plot_annotation(title = cells.types[[i]]) & NoLegend() & NoAxes()
)
}









Check cell proportion for each species:
knitr::kable(prop.table(x = table(Idents(ret.combined), ret.combined@meta.data$orig.ident), margin = 2))
| 0 |
0.2627047 |
0.1535810 |
0.2875831 |
| 1 |
0.5498980 |
0.0758172 |
0.3164080 |
| 2 |
0.0001493 |
0.1855205 |
0.0002217 |
| 3 |
0.0009955 |
0.1458216 |
0.0035477 |
| 4 |
0.0531581 |
0.0808176 |
0.0838137 |
| 5 |
0.0114977 |
0.0783888 |
0.0388027 |
| 6 |
0.0406650 |
0.0552267 |
0.0569845 |
| 7 |
0.0187148 |
0.0577625 |
0.0343681 |
| 8 |
0.0484794 |
0.0443242 |
0.0917960 |
| 9 |
0.0001493 |
0.0342075 |
0.0000000 |
| 10 |
0.0000498 |
0.0232247 |
0.0004435 |
| 11 |
0.0076154 |
0.0156081 |
0.0152993 |
| 12 |
0.0000000 |
0.0117775 |
0.0000000 |
| 13 |
0.0034344 |
0.0089827 |
0.0436807 |
| 14 |
0.0000000 |
0.0109203 |
0.0000000 |
| 15 |
0.0000000 |
0.0101435 |
0.0008869 |
| 16 |
0.0024887 |
0.0039645 |
0.0261641 |
| 17 |
0.0000000 |
0.0039110 |
0.0000000 |
LS0tCnRpdGxlOiAiSW50ZWdyYXRpbmcgUHJpbWF0ZSBEYXRhIGludG8gQW5hbHlzaXMiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KIyBTZXR1cApMb2FkIGxpYnJhcmllcwpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoTWF0cml4KQpsaWJyYXJ5KFNldXJhdCkKbGlicmFyeShjb3dwbG90KQpsaWJyYXJ5KHBhdGNod29yaykKCiMgcGFyYWxsZWxpemF0aW9uCmxpYnJhcnkoZnV0dXJlKQpvcHRpb25zKGZ1dHVyZS5nbG9iYWxzLm1heFNpemU9ICtJbmYpCnBsYW4oKQpgYGAKUHJvY2VzcyBIdW1hbiBEYXRhCmBgYHtyfQppbXBvcnRfcmVtb3RlX2RhdGEgPC0gZnVuY3Rpb24oZmlsZV91cmwsIHR5cGUgPSAidGFibGUiLCBoZWFkZXIgPSBGQUxTRSkgewogIGNvbiA8LSBnemNvbih1cmwoZmlsZV91cmwpKQogIHR4dCA8LSByZWFkTGluZXMoY29uKQogIGlmICh0eXBlID09ICJNTSIpIHsgcmV0dXJuIChyZWFkTU0odGV4dENvbm5lY3Rpb24odHh0KSkpIH0KICBpZiAodHlwZSA9PSAidGFibGUiKSB7IHJldHVybiAocmVhZC50YWJsZSh0ZXh0Q29ubmVjdGlvbih0eHQpLCBoZWFkZXIgPSBoZWFkZXIpKSB9Cn0KY291bnRfbWF0cml4X1VSTCA8LSAiaHR0cHM6Ly9mdHAubmNiaS5ubG0ubmloLmdvdi9nZW8vc2VyaWVzL0dTRTEzN25ubi9HU0UxMzc1Mzcvc3VwcGwvR1NFMTM3NTM3X2NvdW50cy5tdHguZ3oiCmdlbmVfbmFtZXNfVVJMIDwtICJodHRwczovL2Z0cC5uY2JpLm5sbS5uaWguZ292L2dlby9zZXJpZXMvR1NFMTM3bm5uL0dTRTEzNzUzNy9zdXBwbC9HU0UxMzc1MzdfZ2VuZV9uYW1lcy50eHQuZ3oiCnNhbXBsZV9hbm5vdGF0aW9uc19VUkwgPC0gImh0dHBzOi8vZnRwLm5jYmkubmxtLm5paC5nb3YvZ2VvL3Nlcmllcy9HU0UxMzdubm4vR1NFMTM3NTM3L3N1cHBsL0dTRTEzNzUzN19zYW1wbGVfYW5ub3RhdGlvbnMudHN2Lmd6IgoKaHVtYW4uY291bnRfbWF0cml4IDwtIGFzLm1hdHJpeChpbXBvcnRfcmVtb3RlX2RhdGEoY291bnRfbWF0cml4X1VSTCwgdHlwZSA9ICJNTSIpKQpodW1hbi5nZW5lX25hbWVzIDwtIGltcG9ydF9yZW1vdGVfZGF0YShnZW5lX25hbWVzX1VSTCwgdHlwZSA9ICJ0YWJsZSIpCmh1bWFuLnNhbXBsZV9hbm5vdGF0aW9ucyA8LSBpbXBvcnRfcmVtb3RlX2RhdGEoc2FtcGxlX2Fubm90YXRpb25zX1VSTCwgdHlwZSA9ICJ0YWJsZSIsIGhlYWRlciA9IFRSVUUpCmBgYApgYGB7cn0Kcm93bmFtZXMoaHVtYW4uY291bnRfbWF0cml4KSA8LSB0b2xvd2VyKGh1bWFuLmdlbmVfbmFtZXNbLDFdKQpjb2xuYW1lcyhodW1hbi5jb3VudF9tYXRyaXgpIDwtIHRvbG93ZXIoaHVtYW4uc2FtcGxlX2Fubm90YXRpb25zWywxXSkKCmh1bWFuX3JldF9zZXVyYXQgPC0gQ3JlYXRlU2V1cmF0T2JqZWN0KGNvdW50cyA9IGh1bWFuLmNvdW50X21hdHJpeCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGEuZGF0YSA9IGh1bWFuLnNhbXBsZV9hbm5vdGF0aW9ucywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2plY3QgPSAiaHVtYW5fcmV0IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbi5jZWxscyA9IDMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW4uZmVhdHVyZXMgPSAyMDApCmh1bWFuX3JldF9zZXVyYXQKYGBgCgpQcm9jZXNzIE1vdXNlIERhdGEKYGBge3J9Cm1vdXNlLmRhdGEgPC0gUmVhZDEwWChkYXRhLmRpciA9ICJmaWx0ZXJlZF9mZWF0dXJlX2JjX21hdHJpeCIpCmRpbW5hbWVzKG1vdXNlLmRhdGEpW1sxXV0gPC0gdG9sb3dlcihkaW1uYW1lcyhtb3VzZS5kYXRhKVtbMV1dKQpkaW1uYW1lcyhtb3VzZS5kYXRhKVtbMl1dIDwtIHRvbG93ZXIoZGltbmFtZXMobW91c2UuZGF0YSlbWzJdXSkKbW91c2VfcmV0X3NldXJhdCA8LSBDcmVhdGVTZXVyYXRPYmplY3QoY291bnRzID0gbW91c2UuZGF0YSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2plY3QgPSAibW91c2VfcmV0IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbi5jZWxscyA9IDMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW4uZmVhdHVyZXMgPSAyMDApCm1vdXNlX3JldF9zZXVyYXQKYGBgCgpQcm9jZXNzIFByaW1hdGUgRGF0YQpgYGB7YmFzaH0KdXJsPWh0dHBzOi8vZnRwLm5jYmkubmxtLm5paC5nb3YvZ2VvL3Nlcmllcy9HU0UxMThubm4vR1NFMTE4NTQ2L3N1cHBsL0dTRTExODU0Nl9tYWNhcXVlX2ZvdmVhX2FsbF8xMFhfSmFuMjAxOC5SZGF0YS5negp3Z2V0ICR1cmwgLU8gcHJpbWF0ZV9kYXRhL0dTRTExODU0Nl9tYWNhcXVlX2ZvdmVhX2FsbF8xMFhfSmFuMjAxOC5SZGF0YS5negpndW56aXAgcHJpbWF0ZV9kYXRhLyoKYGBgCmBgYHtyfQppbnN0YWxsLnBhY2thZ2VzKCBjKCdkZXZ0b29scycsICdyb3h5Z2VuMicpICkKbGlicmFyeShkZXZ0b29scykKbGlicmFyeShyb3h5Z2VuMikKaW5zdGFsbF9naXRodWIoICdoYi1naXRpZmllZC9jZWxscmFuZ2VyUmtpdCcsCiAgICAgICAgICAgICAgICBhdXRoX3Rva2VuID0gJ3lvdXJfdG9rZW4nICkKYGBgCmBgYHtyfQpsb2FkKCJwcmltYXRlX2RhdGEvR1NFMTE4NTQ2X21hY2FxdWVfZm92ZWFfYWxsXzEwWF9KYW4yMDE4LlJkYXRhIikKCmRpbW5hbWVzKENvdW50Lm1hdF9mb3ZlYSlbWzFdXSA8LSB0b2xvd2VyKGRpbW5hbWVzKENvdW50Lm1hdF9mb3ZlYSlbWzFdXSkKbWFjYXF1ZV9mb3ZlYV9zZXVyYXQgPC0gQ3JlYXRlU2V1cmF0T2JqZWN0KENvdW50Lm1hdF9mb3ZlYSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2plY3QgPSAibWFjYXF1ZV9mb3ZlYSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluLmNlbGxzID0gMywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW4uZmVhdHVyZXMgPSAyMDApCgojIGdpdmUgbWFjYXF1ZSBkdGEgdW5pZm9ybSBuYW1lIGluICJvcmlnLmlkZW50IiBtZXRhZGF0YSBjb2x1bW4KQWRkTWV0YURhdGEobWFjYXF1ZV9mb3ZlYV9zZXVyYXQsIAogICAgICAgICAgICBtZXRhZGF0YSA9IG1hY2FxdWVfZm92ZWFfc2V1cmF0W1sib3JpZy5pZGVudCJdXSwgCiAgICAgICAgICAgIGNvbC5uYW1lID0gIm9yaWcuc2FtcGxlLm5hbWUiKQptYWNhcXVlX2ZvdmVhX3NldXJhdFtbIm9yaWcuaWRlbnQiXV0gPC0gIm1hY2FxdWVfZm92ZWEiCgptYWNhcXVlX2ZvdmVhX3NldXJhdApgYGAKQ2xlYW51cApgYGB7cn0Kcm0oaHVtYW4uY291bnRfbWF0cml4LCBodW1hbi5nZW5lX25hbWVzLCBodW1hbi5zYW1wbGVfYW5ub3RhdGlvbnMpCnJtKGNvdW50X21hdHJpeF9VUkwsIGdlbmVfbmFtZXNfVVJMLCBzYW1wbGVfYW5ub3RhdGlvbnNfVVJMLCBpbXBvcnRfcmVtb3RlX2RhdGEpCnJtKG1vdXNlLmRhdGEpCnJtKENvdW50Lm1hdF9mb3ZlYSwgbWFjYXF1ZV9mb3ZlYSkKYGBgCgoKQ29tYmluZQpgYGB7cn0KIyBjb21iaW5lCnJldC5saXN0IDwtIGxpc3QoaHVtYW4gPSBodW1hbl9yZXRfc2V1cmF0LCBtb3VzZSA9IG1vdXNlX3JldF9zZXVyYXQsIG1hY2FxdWUgPSBtYWNhcXVlX2ZvdmVhX3NldXJhdCkKCiMgcHJlcHJvY2VzcwpyZXQubGlzdCA8LSBsYXBwbHkoWCA9IHJldC5saXN0LCBGVU4gPSBmdW5jdGlvbih4KSB7CiAgICB4IDwtIE5vcm1hbGl6ZURhdGEoeCwgdmVyYm9zZSA9IEZBTFNFKQogICAgeCA8LSBGaW5kVmFyaWFibGVGZWF0dXJlcyh4LCBzZWxlY3Rpb24ubWV0aG9kID0gInZzdCIsIG5mZWF0dXJlcyA9IDIwMDAsIHZlcmJvc2UgPSBGQUxTRSkKfSkKCiMgY2xlYW51cApybShodW1hbl9yZXRfc2V1cmF0LCBtb3VzZV9yZXRfc2V1cmF0LCBtYWNhcXVlX2ZvdmVhX3NldXJhdCkKYGBgCgojIEludGVncmF0aW9uCmBgYHtyfQpwbGFuKCJtdWx0aXByb2Nlc3MiLCB3b3JrZXJzID0gNCkKcmV0LmFuY2hvcnMgPC0gRmluZEludGVncmF0aW9uQW5jaG9ycyhvYmplY3QubGlzdCA9IHJldC5saXN0LCBkaW1zID0gMTo1MCwgIGFuY2hvci5mZWF0dXJlcyA9IDEwMDApCnBsYW4oIm11bHRpcHJvY2VzcyIsIHdvcmtlcnMgPSAxKQpyZXQuY29tYmluZWQgPC0gSW50ZWdyYXRlRGF0YShhbmNob3JzZXQgPSByZXQuYW5jaG9ycywgZGltcyA9IDE6NTApCmBgYAoKIyBJbnRlZ3JhdGVkIEFuYWx5c2lzCmBgYHtyfQpwbGFuKCJtdWx0aXByb2Nlc3MiLCB3b3JrZXJzID0gNCkKCkRlZmF1bHRBc3NheShyZXQuY29tYmluZWQpIDwtICJpbnRlZ3JhdGVkIgoKIyBSdW4gdGhlIHN0YW5kYXJkIHdvcmtmbG93IGZvciB2aXN1YWxpemF0aW9uIGFuZCBjbHVzdGVyaW5nCnJldC5jb21iaW5lZCA8LSBTY2FsZURhdGEocmV0LmNvbWJpbmVkLCB2ZXJib3NlID0gRkFMU0UpCnJldC5jb21iaW5lZCA8LSBSdW5QQ0EocmV0LmNvbWJpbmVkLCBucGNzID0gNTAsIHZlcmJvc2UgPSBGQUxTRSkKIyB0LVNORSBhbmQgQ2x1c3RlcmluZwpyZXQuY29tYmluZWQgPC0gUnVuVU1BUChyZXQuY29tYmluZWQsIHJlZHVjdGlvbiA9ICJwY2EiLCBkaW1zID0gMTozNSkKcmV0LmNvbWJpbmVkIDwtIEZpbmROZWlnaGJvcnMocmV0LmNvbWJpbmVkLCByZWR1Y3Rpb24gPSAicGNhIiwgZGltcyA9IDE6MzUpCnJldC5jb21iaW5lZCA8LSBGaW5kQ2x1c3RlcnMocmV0LmNvbWJpbmVkLCByZXNvbHV0aW9uID0gMC4wNzUpCmBgYAojIFVNQVAgVmlzdWFsaXphdGlvbgpgYGB7ciB3YXJuaW5nPUZBTFNFfQpEaW1QbG90KHJldC5jb21iaW5lZCwgcmVkdWN0aW9uID0gInVtYXAiLCBncm91cC5ieSA9ICJvcmlnLmlkZW50IikKRGltUGxvdChyZXQuY29tYmluZWQsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgbGFiZWwgPSBUUlVFKQpgYGAKYGBge3IsIGZpZy5oZWlnaHQgPSA0LCBmaWcud2lkdGggPSAzfQpEaW1QbG90KHJldC5jb21iaW5lZCwgcmVkdWN0aW9uID0gInVtYXAiLCBzcGxpdC5ieSA9ICJvcmlnLmlkZW50IiwgbmNvbCA9IDEpCmBgYAoKIyBJZGVudGlmeSBDbHVzdGVycyB3aXRoIENhbm9uaWNhbCBNYXJrZXJzCmBgYHtyfQpEZWZhdWx0QXNzYXkocmV0LmNvbWJpbmVkKSA8LSAiUk5BIgoKZmVhdHVyZXMgPC0gdG9sb3dlcihjKCJQZGU2YSIsIkduYXQyIiwiTmVmbCIsIkNhbWsyYiIsIlRoeTEiLCJHYWQxIiwiU2xjNmE5IiwKICAgICAgICAgICAgICAgICAgICAgICJQY3NrNiIsIlRycG0xIiwiU2VwdDQiLCJHbHVsIiwiQXJyMyIsIkMxcWEiLCJUbTRzZjEiLCAiTWdwIikpCgpGZWF0dXJlUGxvdChvYmplY3QgPSByZXQuY29tYmluZWQsIAogICAgICAgICAgICBmZWF0dXJlcyA9IGZlYXR1cmVzLCAKICAgICAgICAgICAgcHQuc2l6ZSA9IDAuMSwKICAgICAgICAgICAgY29scyA9IGMoImxpZ2h0Z3JleSIsICIjRjI2OTY5IiksCiAgICAgICAgICAgIG1pbi5jdXRvZmYgPSAicTkiLAogICAgICAgICAgICBjb21iaW5lID0gVFJVRSkgJiBOb0xlZ2VuZCgpICYgTm9BeGVzCgojIENvd3Bsb3QgbWV0aG9kOiBtYWtlIHN1cmUgdG8gY2hhbmdlIHRvICJjb21iaW5lID0gRkFMU0UiIGFuZCByZW1vdmUgIiYgTm9MZWdlbmQoKSAmIE5vQXhlcyIKCiMgZm9yKGkgaW4gMTpsZW5ndGgocCkpIHsKIyAgIHBbW2ldXSA8LSBwW1tpXV0gKyBOb0xlZ2VuZCgpICsgTm9BeGVzKCkKIyB9CiMgCiMgY293cGxvdDo6cGxvdF9ncmlkKHBsb3RsaXN0ID0gcCwgbmNvbD0zKQpgYGAKCiogUm9kIDogcGRlNmEKKiBBQyAoYW1hY3JpbmUgY2VsbCkgOiBnYWQxLCBzbGM2YTkKKiBNRyAoTcO8bGxlciBnbGlhKSA6IGdsdWwKKiBCQyAoYmlwb2xhciBjZWxsKSA6IFRycG0sIGNhbWsyYgoqIENDIChjb25lIGNlbGwpIDogZ25hdDIsIGFycjMKKiBSR0MgKHJldGluYWwgZ2FuZ2xpYWwgY2VsbCkgOiBuZWZsLCB0aHkxCiogVkMgKHZhc2N1bGFyIGNlbGwpIDogbWdwLCB0bTRzZjEKKiBNIChtaWNyb2dsaWEpIDogYzFxYQoqIEhDIChob3Jpem9udGFsIGNlbGwpIDogc2VwdDQKCk1hcmtlcnMgd2VyZSBkZXRlcm1pbmVkIGZyb20gW3RoaXNdKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvczQxNDY3LTAxOS0xMjc4MC04KSBwYXBlciBhbmQgb3RoZXIgc291cmNlcy4KYGBge3J9CnJldC5jb21iaW5lZCA8LSBSZW5hbWVJZGVudHMocmV0LmNvbWJpbmVkLCBgMGAgPSAiTUciLCBgMWAgPSAiUm9kIiwgYDJgID0gIlJHQyIsIAogICAgYDNgID0gIlJHQyIsIGA0YCA9ICJCQyIsIGA1YCA9ICJDQyIsIGA2YCA9ICJCQyIsIGA3YCA9ICJBQyIsIGA4YCA9ICJCQyIsIGA5YCA9ICJSR0MiLCAKICAgIGAxMGAgPSAiUkdDIiwgYDExYD0gIkhDIiwgYDEyYCA9ICJNRyIsIGAxM2AgPSAiVkMiLCBgMTRgID0gIlJHQyIsIGAxNWAgPSAiUkdDIiwgYDE2YCA9ICJNIiwgYDE3YCA9ICJSR0MiKQoKRGltUGxvdChyZXQuY29tYmluZWQsIGxhYmVsID0gVFJVRSkKYGBgCgoKIyBGaW5kIERpZmZlcmVudGlhbGx5IEV4cHJlc3NlZCBHZW5lcwpgYGB7cn0KY2VsbHMudHlwZXMgPC0gYygiUm9kIiwgIkJDIiwgIk1HIiwgIlJHQyIsICJDQyIsICJBQyIsICJWQyIsICJIQyIsICJNIikKdGhlbWVfc2V0KHRoZW1lX2Nvd3Bsb3QoKSkKCmNlbGxfdHlwZV9hdmcgPC0gZnVuY3Rpb24oc2V1cmF0LmNvbWJpbmVkLCBpZGVudCkgewogIGNlbGxzLnggPC0gc3Vic2V0KHNldXJhdC5jb21iaW5lZCwgaWRlbnRzID0gaWRlbnQpCiAgSWRlbnRzKGNlbGxzLngpIDwtICJvcmlnLmlkZW50IgogIGNlbGxzLnguYXZnIDwtIGxvZzFwKEF2ZXJhZ2VFeHByZXNzaW9uKGNlbGxzLngsIHZlcmJvc2UgPSBGQUxTRSkkUk5BKQogIGNlbGxzLnguYXZnJGdlbmUgPC0gcm93bmFtZXMoY2VsbHMueC5hdmcpCiAgcmV0dXJuKGNlbGxzLnguYXZnKQp9CgpjZWxscy5wbG90IDwtIGFzLmxpc3QoY2VsbHMudHlwZXMpCmNlbGxzLnBsb3QgPC0gbGFwcGx5KGNlbGxzLnBsb3QsIEZVTiA9IGZ1bmN0aW9uKHgpIHsKICBjZWxscy54LmF2ZyA8LSBjZWxsX3R5cGVfYXZnKHJldC5jb21iaW5lZCwgaWRlbnQgPSB4KQogIHggPC0gZ2dwbG90KGNlbGxzLnguYXZnLCBhZXMoaHVtYW5fcmV0LCBtb3VzZV9yZXQpKSArIGdlb21fcG9pbnQoc2l6ZSA9IDAuMSkgKyBnZ3RpdGxlKHgpCiAgcmV0dXJuKHgpCn0pCgojIEZvciBpbmRpdmlkdWFsIHBsb3RzCiMgZm9yIChwIGluIGNlbGxzLnBsb3QpIHsKIyAgIHByaW50KHApCiMgfQoKIyBGb3IgZ3JpZCBwbG90CmNvd3Bsb3Q6OnBsb3RfZ3JpZChwbG90bGlzdCA9IGNlbGxzLnBsb3QsIG5jb2wgPSAzKQpgYGAKYGBge3J9CnJldC5jb21iaW5lZCRjZWxsdHlwZS5vcmdhbmlzbSA8LSBwYXN0ZShJZGVudHMocmV0LmNvbWJpbmVkKSwgcmV0LmNvbWJpbmVkJG9yaWcuaWRlbnQsIHNlcCA9ICJfIikKcmV0LmNvbWJpbmVkJGNlbGx0eXBlIDwtIElkZW50cyhyZXQuY29tYmluZWQpCklkZW50cyhyZXQuY29tYmluZWQpIDwtICJjZWxsdHlwZS5vcmdhbmlzbSIKYGBgCmBgYHtyfQpjZWxscy5kaWZmZ2VuZXMgPC0gYXMubGlzdChjZWxscy50eXBlcykKY2VsbHMuZGlmZmdlbmVzIDwtIGxhcHBseShjZWxscy5kaWZmZ2VuZXMsIEZVTiA9IGZ1bmN0aW9uKHgpIHsKICBsYWJfaHVtYW4gPC0gc3ByaW50ZigiJXNfaHVtYW5fcmV0IiwgeCkKICBsYWJfbW91c2UgPC0gc3ByaW50ZigiJXNfbW91c2VfcmV0IiwgeCkKICByZXR1cm4oRmluZE1hcmtlcnMocmV0LmNvbWJpbmVkLCBpZGVudC4xID0gbGFiX2h1bWFuLCBpZGVudC4yID0gbGFiX21vdXNlLCB2ZXJib3NlID0gRkFMU0UpKQp9KQpgYGAKVGFibGVzIHdpdGggdGhlIG1vc3QgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGdlbmVzIGluIGVhY2ggY2VsbCBzdWJ0eXBlOgpgYGB7cn0KZm9yKGkgaW4gc2VxX2Fsb25nKGNlbGxzLmRpZmZnZW5lcykpIHsKICBwcmludChrbml0cjo6a2FibGUoaGVhZChjZWxscy5kaWZmZ2VuZXNbW2ldXSksY2FwdGlvbj1jZWxscy50eXBlc1tbaV1dKSkKfQpgYGAKU2F2ZSBhcyBjc3YgZmlsZXMKYGBge3J9CmZvcihpIGluIHNlcV9hbG9uZyhjZWxscy5kaWZmZ2VuZXMpKSB7CiAgd3JpdGUuY3N2KGNlbGxzLmRpZmZnZW5lc1tbaV1dLCBzcHJpbnRmKCJyZXN1bHRzLyVkXyVzLmNzdiIsIGksIGNlbGxzLnR5cGVzW1tpXV0pKQp9CmBgYAoKYGBge3Igd2FybmluZz1GQUxTRX0KZ2VuZXNfdG9fcGxvdCA8LSAzCmZvciAoaSBpbiBzZXFfYWxvbmcoY2VsbHMudHlwZXMpKSB7CiAgcHJpbnQoRmVhdHVyZVBsb3Qob2JqZWN0ID0gcmV0LmNvbWJpbmVkLCAKICAgICAgICAgICAgICBmZWF0dXJlcyA9IHJvd25hbWVzKGNlbGxzLmRpZmZnZW5lc1tbaV1dKVsxOmdlbmVzX3RvX3Bsb3RdLCAKICAgICAgICAgICAgICBzcGxpdC5ieSA9ICJvcmlnLmlkZW50IiwgCiAgICAgICAgICAgICAgbWF4LmN1dG9mZiA9IDMsIAogICAgICAgICAgICAgIGNvbHMgPSBjKCJncmV5IiwgInJlZCIpLAogICAgICAgICAgICAgIHB0LnNpemUgPSAwLjA3LAogICAgICAgICAgICAgIGNvbWJpbmUgPSBUUlVFLAogICAgICAgICAgICAgIGxhYmVsLnNpemUgPSAwLjUKICAgICAgICAgICAgICApICsgcGxvdF9hbm5vdGF0aW9uKHRpdGxlID0gY2VsbHMudHlwZXNbW2ldXSkgJiBOb0xlZ2VuZCgpICYgTm9BeGVzKCkKICAgICAgICApCn0KYGBgCgpDaGVjayBjZWxsIHByb3BvcnRpb24gZm9yIGVhY2ggc3BlY2llczoKYGBge3J9CmtuaXRyOjprYWJsZShwcm9wLnRhYmxlKHggPSB0YWJsZShJZGVudHMocmV0LmNvbWJpbmVkKSwgcmV0LmNvbWJpbmVkQG1ldGEuZGF0YSRvcmlnLmlkZW50KSwgbWFyZ2luID0gMikpCmBgYAoK